/**
 * All rights Reserved, Designed By junglead.cn
 * @package cn.junglead.common.utils
 * @title IdWorker.java
 * @date 2020年12月3日
 */
package cn.junglead.common.utils;

import cn.junglead.common.utils.exception.ActiveException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * 分布式id生成工具类
 * @author Gary.Wang
 * @version 1.0
 */
@Slf4j
@Data
public final class IdWorker {

	private static final long WORKER_ID_BITS = 5L;
	private static final long DATACENTER_ID_BITS = 5L;
	private static final long SEQUENCE_BITS = 12L;
	private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
	private static long lastTimestamp = -1L;

    /**
     * 工厂编号
     */
	private long workerId;
    /**
     * 数据中心编号
     */
    private long datacenterId;
    /**
     * 序列编号
     */
    private long sequence;

	/**
	 * 构造函数，初始化一个雪花算法其
	 * @param workerId 工厂id
	 * @param datacenterId 数据id
	 * @param sequence 序列
	 */
	public IdWorker(long workerId, long datacenterId, long sequence) {
		// sanity check for workerId
		// 这儿不就检查了一下，要求就是你传递进来的机房id和机器id不能超过32，不能小于0
		// 这个是二进制运算，就是 5 bit最多只能有31个数字，也就是说机器id最多只能是32以内
		long maxWorkerId = ~(-1L << WORKER_ID_BITS);
		if (workerId > maxWorkerId || workerId < 0) {
			throw new IllegalArgumentException(
					String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
		}
		// 这个是一个意思，就是 5 bit最多只能有31个数字，机房id最多只能是32以内
		long maxDatacenterId = ~(-1L << DATACENTER_ID_BITS);
		if (datacenterId > maxDatacenterId || datacenterId < 0) {
			throw new IllegalArgumentException(
					String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
		}
		log.debug("worker starting. timestamp left shift {}, datacenter id bits {}, worker id bits {}, sequence bits {}, workerId {}",
				TIMESTAMP_LEFT_SHIFT, DATACENTER_ID_BITS, WORKER_ID_BITS, SEQUENCE_BITS, workerId);
		this.setWorkerId(workerId);
		this.setDatacenterId(datacenterId);
		this.setSequence(sequence);
	}
	/**
	 * 线程安全的获取id的方法
	 * @return 返回生成的id
	 */
	public synchronized long nextId() {
		// 获取当前时间戳
		long timestamp = timeGen();
		if (timestamp < lastTimestamp) {
			log.error("clock is moving backwards.  Rejecting requests until {}.", lastTimestamp);
			throw new ActiveException(IdWorker.class, String.format(
					"Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
		}
		if (lastTimestamp == timestamp) {
			// 这个意思是说一个毫秒内最多只能有4096个数字
			// 无论你传递多少进来，这个位运算保证始终就是在4096这个范围内，避免你自己传递个sequence超过了4096这个范围
			long sequenceMask = ~(-1L << SEQUENCE_BITS);
			setSequence((getSequence() + 1) & sequenceMask);
			if (getSequence() == 0) {
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
			setSequence(0);
		}

		// 这儿记录一下最近一次生成id的时间戳，单位是毫秒
		lastTimestamp = timestamp;

		// 这儿就是将时间戳左移，放到 41 bit那儿；
		// 将机房 id左移放到 5 bit那儿；
		// 将机器 id左移放到5 bit那儿；
		// 将序号放最后12 bit；
		// 最后拼接起来成一个 64 bit的二进制数字，转换成 10 进制就是个 long 型
		long twepoch = 1288834974657L;
		long datacenterIdShift = SEQUENCE_BITS + WORKER_ID_BITS;
		return ((timestamp - twepoch) << TIMESTAMP_LEFT_SHIFT) | (getDatacenterId() << datacenterIdShift)
				| (getWorkerId() << SEQUENCE_BITS) | getSequence();
	}

	/**
	 * 1.2 下一次便宜时间
	 * @param lastTimestamp 最晚时间戳
	 * @return 返回下一个时间
	 */
	private long tilNextMillis(long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}

    /**
     * 1.1 获取当前时间戳
     * @return 返回当前时间戳
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }
}
